﻿using System;
using System.Drawing;
using System.IO;
using System.Linq;
using AForge.Neuro;
using AForge.Neuro.Learning;
using Accord.Statistics.Analysis;

namespace NetsTest
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            //////////////////////////////////////////////////////////////////////////
            //Loading data
            Console.WriteLine("Loading {0:T}", DateTime.Now);

            string dirPath = "Data";
            int dataSize = 300;
            Bitmap[] bitmaps = new Bitmap[dataSize];
            bool[] validity = new bool[dataSize];
            double[][] angles = new double[dataSize][];
            double[][] position3D = new double[dataSize][];
            double[][] segmentsX = new double[dataSize][];
            double[][] segmentsY = new double[dataSize][];

            int imageSize = 50;

            for (int i = 0; i < dataSize; ++i)
            {
                validity[i] = true;
            }

            for (int i = 0; i < dataSize; ++i)
            {
                string filePath = dirPath + String.Format("/images/{0:D3}.png", 10*i + 1);
                FileStream fileStream;

                try
                {
                    fileStream = new FileStream(filePath, FileMode.Open);
                }
                catch (System.Exception)
                {
                    fileStream = null;
                    validity[i] = false;
                    continue;
                }

                Bitmap bitmap = new Bitmap(fileStream);
                bitmaps[i] = bitmap;
                fileStream.Close();
            }

            StreamReader reader = new StreamReader(dirPath + "/output.csv");

            for (int i = 0; i < dataSize; ++i)
            {
                for (int j = 0; j < 9; ++j)
                    reader.ReadLine();
                string line = reader.ReadLine();
                if (line == null)
                {
                    validity[i] = false;
                    break;
                }

                if (line.StartsWith("Invalid"))
                {
                    validity[i] = false;
                    continue;
                }

                string[] values = line.Split(';');
                double alpha = Double.Parse(values[0]);
                double beta = Double.Parse(values[1]);
                angles[i] = new double[2];
                angles[i][0] = alpha;
                angles[i][1] = beta;

                position3D[i] = new double[3];
                position3D[i][0] = Double.Parse(values[4]);
                position3D[i][1] = Double.Parse(values[5]);
                position3D[i][2] = Double.Parse(values[6]);

                segmentsX[i] = new double[10];
                segmentsY[i] = new double[10];

                //zero
                for (int j = 0; j < 10; ++j)
                {
                    segmentsX[i][j] = 0.0;
                    segmentsY[i][j] = 0.0;
                }

                int segmentX = (int)((alpha + Math.PI / 2) / (Math.PI / 10));
                int segmentY = (int)((beta + Math.PI / 2) / (Math.PI / 10));

                segmentX = Math.Min(9, Math.Max(0, segmentX));
                segmentY = Math.Min(9, Math.Max(0, segmentY));

                segmentsX[i][segmentX] = 1.0;
                segmentsY[i][segmentY] = 1.0;
            }

            //////////////////////////////////////////////////////////////////////////
            //Preparing data
            Console.WriteLine("Preparing data {0:T}", DateTime.Now);
            //int samplesCount = dataSize / 30;
            int samplesCount = 0;
            for (int i = 0; i < dataSize; ++i)
            {
                if (validity[i])
                    samplesCount++;
            }

            //input and output
            double[,] input = new double[samplesCount,imageSize * imageSize];
            double[,] output = new double[samplesCount,20];
            double[,] position3DValidated = new double[samplesCount, 3];
            int index = 0;

            //Random random = new Random();
            for (int i = 0; i < dataSize; ++i)
            {
                ////random index
                //int idx;
                //do
                //{
                //    idx = random.Next(30) + i * 30;
                //} while (validity[idx] != true);

                //copy bitmap
                if (validity[i] == false)
                    continue;

                for (int y = 0; y < imageSize; ++y)
                {
                    for (int x = 0; x < imageSize; ++x)
                    {
                        //Color c = bitmaps[idx].GetPixel(x, y);
                        Color c = bitmaps[i].GetPixel(x, y);
                        double value = (c.R + c.G + c.B) / (3 * 255.0);
                        input[index, y * imageSize + x] = value;
                    }
                }

                for (int j = 0; j < 10; ++j)
                {
                    output[index, j] = segmentsX[i][j];
                    output[index, j + 10] = segmentsX[i][j];
                }

                position3DValidated[index, 0] = position3D[i][0];
                position3DValidated[index, 1] = position3D[i][1];
                position3DValidated[index, 2] = position3D[i][2];
                index++;
            }

            //////////////////////////////////////////////////////////////////////////
            //Split data to learning set and testing set

            double learningPart = 0.9;
            int learningSamples = (int)(samplesCount * learningPart + 0.5);
            int testingSamples = samplesCount - learningSamples;

            double[,] learningInput = new double[learningSamples, imageSize * imageSize];
            double[,] learningOutput = new double[learningSamples, 20];
            double[,] testingInput = new double[testingSamples, imageSize * imageSize];
            double[,] testingOutput = new double[testingSamples, 20];
            double[,] position3DTesting = new double[testingSamples, 3];

            //Copy data
            for (int i = 0; i < learningSamples; ++i)
            {
                for (int j = 0; j < imageSize * imageSize; ++j)
                {
                    learningInput[i, j] = input[i, j];
                }

                for (int j = 0; j < 20; ++j)
                {
                    learningOutput[i, j] = output[i, j];
                }
            }

            for (int i = 0; i < testingSamples; ++i)
            {
                for (int j = 0; j < imageSize * imageSize; ++j)
                {
                    testingInput[i, j] = input[i + learningSamples, j];
                }

                for (int j = 0; j < 20; ++j)
                {
                    testingOutput[i, j] = output[i + learningSamples, j];
                }

                position3DTesting[i, 0] = position3DValidated[i + learningSamples, 0];
                position3DTesting[i, 1] = position3DValidated[i + learningSamples, 1];
                position3DTesting[i, 2] = position3DValidated[i + learningSamples, 2];
            }

            //PCA
            Console.WriteLine("PCA {0:T}", DateTime.Now);
            PrincipalComponentAnalysis pca = new PrincipalComponentAnalysis(learningInput, AnalysisMethod.Standardize);
            pca.Compute();

            int dimensions = 200;

            double[,] transformedLearningInput = pca.Transform(learningInput, dimensions);
            double[,] transformedTestingInput = pca.Transform(testingInput, dimensions);

            //Transform
            Console.WriteLine("Transform {0:T}", DateTime.Now);
            double minInput = Double.MaxValue;
            double maxInput = Double.MinValue;

            for (int i = 0; i < learningSamples; ++i)
            {
                for (int j = 0; j < imageSize * imageSize; ++j)
                {
                    minInput = Math.Min(minInput, learningInput[i,j]);
                    maxInput = Math.Max(maxInput, learningInput[i,j]);
                }
            }

            for (int i = 0; i < learningSamples; ++i)
            {
                for (int j = 0; j < imageSize * imageSize; ++j)
                {
                    learningInput[i,j] = (learningInput[i,j] - minInput) / (maxInput - minInput) * 1.6 - 0.8;
                }
            }

            for (int i = 0; i < testingSamples; ++i)
            {
                for (int j = 0; j < imageSize * imageSize; ++j)
                {
                    testingInput[i,j] = (testingInput[i,j] - minInput) / (maxInput - minInput) * 1.6 - 0.8;
                }
            }

            //////////////////////////////////////////////////////////////////////////
            //Preparing neural network
            //int inputLayer = 50 * 50 + 1;
            int inputLayer = dimensions;
            int outputLayer = 20;

            ActivationNetwork network = new ActivationNetwork(new BipolarSigmoidFunction(), inputLayer, 100, outputLayer);
            BackPropagationLearning teacher = new BackPropagationLearning(network);

            double learningRate = 1.0;
            //double momentum = 0.03;

            teacher.LearningRate = learningRate;
            //teacher.Momentum = momentum;

            //////////////////////////////////////////////////////////////////////////
            //Learning

            Console.WriteLine("Learning {0:T}", DateTime.Now);
            //Convert to jagged arrays
            double[][] jaggedLearningInput = new double[learningSamples][];
            double[][] jaggedTestingInput = new double[testingSamples][];
            double[][] jaggetLearningOutput = new double[learningSamples][];
            double[][] jaggetTestingOutput = new double[testingSamples][];

            for (int i = 0; i < learningSamples; ++i)
            {
                jaggedLearningInput[i] = new double[dimensions];
                jaggetLearningOutput[i] = new double[20];

                for (int j = 0; j < dimensions; ++j)
                {
                    jaggedLearningInput[i][j] = transformedLearningInput[i, j];
                }

                for (int j = 0; j < 20; ++j)
                {
                    jaggetLearningOutput[i][j] = learningOutput[i, j];
                }
            }

            for (int i = 0; i < testingSamples; ++i)
            {
                jaggedTestingInput[i] = new double[dimensions];
                jaggetTestingOutput[i] = new double[20];

                for (int j = 0; j < dimensions; ++j)
                {
                    jaggedTestingInput[i][j] = transformedTestingInput[i, j];
                }

                for (int j = 0; j < 20; ++j)
                {
                    jaggetTestingOutput[i][j] = testingOutput[i, j];
                }
            }

            double error = 0;
            double lastError = 0;
            for (int i = 0; i < 1000; ++i)
            {
                lastError = error;
                error = teacher.RunEpoch(jaggedLearningInput, jaggetLearningOutput) / learningSamples;

                //if (lastError > error)
                    teacher.LearningRate *= 0.995;
                //else
                //    teacher.LearningRate /= 0.995;

                if (i % 10 == 0)
                    Console.WriteLine("{0:d4}: {1:F5}   {2:F5}", i, error, teacher.LearningRate);
            }

            Console.WriteLine("End of learning\nError: {0:F8}\n{1:T}",error, DateTime.Now);

            Console.ReadKey(false);

            //////////////////////////////////////////////////////////////////////////
            //Testing
            Console.WriteLine("Testing {0:T}", DateTime.Now);
            StreamWriter writer = new StreamWriter("data.txt");

            for (int i = 0; i < testingSamples; ++i)
            {
                double[] result = network.Compute(jaggedTestingInput[i]);

                double max1 = result[0];
                double max2 = result[10];
                int index1 = 0;
                int index2 = 0;
                int testingIndex1 = 0;
                int testingIndex2 = 0;

                for (int j = 0; j < 10; ++j)
                {
                    if (max1 < result[j])
                    {
                        index1 = j;
                        max1 = result[j];
                    }

                    if (max2 < result[10 + j])
                    {
                        index2 = j;
                        max2 = result[10 + j];
                    }

                    if (jaggetTestingOutput[i][j] > 0.5)
                    {
                        testingIndex1 = j;
                    }

                    if (jaggetTestingOutput[i][j + 10] > 0.5)
                    {
                        testingIndex2 = j;
                    }
                }

                Console.WriteLine("{0}, {1}, {2}, {3}", index1, index2, testingIndex1, testingIndex2);
                writer.WriteLine("{0}, {1}, {2}, {3}", index1, index2, testingIndex1, testingIndex2);

                //Console.WriteLine("{0:F8} {1:F8} {2:F8} {3:F8} {4:F8} {5:F8} {6:F8}", result[0], result[1], jaggetTestingOutput[i][0], jaggetTestingOutput[i][1], position3DTesting[i, 0], position3DTesting[i, 1], position3DTesting[i, 2]);
                //writer.WriteLine("{0:F8} {1:F8} {2:F8} {3:F8} {4:F8} {5:F8} {6:F8}", result[0], result[1], jaggetTestingOutput[i][0], jaggetTestingOutput[i][1], position3DTesting[i, 0], position3DTesting[i, 1], position3DTesting[i, 2]);
            }

            writer.Close();

            writer = new StreamWriter("minMax.txt");
            writer.WriteLine("minInput: {0:F8}", minInput);
            writer.WriteLine("maxInput: {0:F8}", maxInput);
            writer.Close();

            Console.ReadKey(false);

            //////////////////////////////////////////////////////////////////////////
            //Display results
        }
    }
}